package com.neihe.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.neihe.common.base.ApiException;
import com.neihe.common.base.ResultCode;
import com.neihe.common.constant.Const;
import com.neihe.common.constant.FileConst;
import com.neihe.common.enums.RoleEnum;
import com.neihe.common.enums.UserStatusEnum;
import com.neihe.common.utils.FlowUtils;
import com.neihe.common.utils.NetworkUtils;
import com.neihe.entity.User;
import com.neihe.entity.UserFile;
import com.neihe.entity.dto.admin.UpUserStatusDTO;
import com.neihe.entity.dto.admin.UserQueryPageDTO;
import com.neihe.entity.dto.file.CreateFolderDTO;
import com.neihe.entity.dto.user.CheckCodeDTO;
import com.neihe.entity.dto.user.LoginFormDTO;
import com.neihe.entity.dto.user.RegisterFormDTO;
import com.neihe.entity.dto.user.ResetPwdFormDTO;
import com.neihe.entity.vo.admin.UserQueryPageVO;
import com.neihe.entity.vo.user.UseSpaceVO;
import com.neihe.entity.vo.user.UserInfoVO;
import com.neihe.listenner.event.SendCode;
import com.neihe.mapper.UserMapper;
import com.neihe.service.IUserFileService;
import com.neihe.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Objects;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author neihe
 * @since 2025-01-04
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, ApplicationContextAware {

    @Lazy
    @Autowired
    private IUserFileService userFileService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private FlowUtils flowUtils;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void sendCode(String email, HttpServletRequest request) {
        String address = NetworkUtils.getIpAddress(request);
        synchronized (address.intern()) {
            if (!flowUtils.verifyLimit(address)) {
                throw new ApiException("1分钟内仅能发送一次验证码");
            }

            // 生成邮箱验证码
            String code = RandomUtil.randomNumbers(4);

            // 发送邮箱验证码
            SendCode event = new SendCode(this, email, code);
            applicationContext.publishEvent(event);
        }
    }

    @Override
    public void checkCode(CheckCodeDTO requestParam) {
        String email = requestParam.getEmail();
        String code = requestParam.getCode();

        // 检查验证码有效性
        String key = Const.VERIFY_EMAIL_CODE + email;
        verifyCode(key, code);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(RegisterFormDTO requestParam) {
        try {
            String email = requestParam.getEmail();
            String password = requestParam.getPassword();
            String code = requestParam.getCode();

            User account = query().eq("email", email).one();
            if (account != null) {
                throw new ApiException("该邮箱已被使用");
            }

            // 检查验证码有效性
            String key = Const.VERIFY_EMAIL_CODE + email;
            verifyCode(key, code);

            // 删除已使用的验证码
            redisTemplate.delete(key);

            // 加密
            String encrypt = BCrypt.hashpw(password, BCrypt.gensalt());

            User user = new User();
            user.setEmail(email);
            user.setPassword(encrypt);
            user.setRole(RoleEnum.user.getRole());
            user.setUseSpace(0L);
            user.setTotalSpace(FileConst.MAX_SIZE);
            user.setRegisterTime(new Date());
            user.setStatus(UserStatusEnum.enable.getStatus());

            if (!save(user)) {
                throw new ApiException("内部错误，注册失败");
            }

            // 创建默认文件夹
            createUserRootFolder(user);

        } catch (Exception e) {
            throw new ApiException(e.getMessage());
        }
    }

    /**
     * 创建用户的根目录信息
     */
    private void createUserRootFolder(User user) {
        CreateFolderDTO context = new CreateFolderDTO();
        context.setUserId(user.getId());
        context.setParentId(FileConst.TOP_PARENT_ID);
        context.setFolderName(FileConst.ALL_FILE_CN_STR);
        userFileService.createFolder(context);
    }

    @Override
    public UserInfoVO login(LoginFormDTO requestParam) {
        String email = requestParam.getEmail();
        String password = requestParam.getPassword();

        User user = query().eq("email", email).one();
        if (user == null) {
            throw new ApiException("用户不存在");
        }

        if (Objects.equals(user.getStatus(), UserStatusEnum.disable.getStatus())) {
            throw new ApiException("账号已经被禁用");
        }

        if (!BCrypt.checkpw(password, user.getPassword())) {
            throw new ApiException("密码不正确");
        }

        StpUtil.login(user.getId());
        UserFile userFile = userFileService.getUserRootFile(StpUtil.getLoginIdAsLong());

        if (userFile == null) {
            throw new ApiException("查询用户根文件夹信息失败");
        }

        UserInfoVO vo = BeanUtil.toBean(user, UserInfoVO.class);
        vo.setToken(StpUtil.getTokenValue());
        vo.setRootFileId(userFile.getFileId());
        return vo;
    }

    @Override
    public void deductSpace(Long userId, Long fileSize) {
        User user = query().eq("id", userId).one();

        // 先检查总空间是否足够
        if (user.getTotalSpace() < user.getUseSpace() + fileSize) {
            throw new ApiException(ResultCode.NEED_EXPAND);
        }

        // 扣减容量（更新已使用空间）
        boolean updated = update()
                .setSql(String.format("use_space = use_space + %d", fileSize))
                .eq("id", userId)
                .update();

        if (!updated) {
            throw new ApiException("内部错误，扣减容量失败");
        }
    }

    @Override
    public void addSpace(Long userId, Long fileSize) {
        // 释放空间，获得容量
        boolean updated = update()
                .setSql(String.format("use_space = use_space - %d", fileSize))
                .eq("id", userId)
                .ge("use_space", fileSize)
                .update();

        if (!updated) {
            throw new ApiException("内部错误，释放容量失败");
        }

    }

    @Override
    public UseSpaceVO getUseSpace(Long userId) {
        User user = query().eq("id", userId).one();
        return BeanUtil.toBean(user, UseSpaceVO.class);
    }

    @Override
    public void resetPassword(ResetPwdFormDTO requestParam) {
        String email = requestParam.getEmail();
        String password = requestParam.getPassword();
        String code = requestParam.getCode();

        // 检查验证码有效性
        String key = Const.VERIFY_EMAIL_CODE + email;
        verifyCode(key, code);

        // 删除已使用的验证码
        redisTemplate.delete(key);

        User user = query().eq("email", email).one();

        if (user == null) {
            throw new ApiException("用户不存在");
        }

        String encrypt = BCrypt.hashpw(password, BCrypt.gensalt());
        boolean updated = update()
                .eq("id", user.getId())
                .set("password", encrypt)
                .update();

        if (!updated) {
            throw new ApiException("内部错误，重置密码失败！");
        }
    }

    @Override
    public IPage<UserQueryPageVO> getUserList(UserQueryPageDTO requestParam) {
        IPage<User> resultPage = baseMapper.pageUser(requestParam);
        return resultPage.convert(each -> BeanUtil.toBean(each, UserQueryPageVO.class));
    }

    @Override
    public void upUserStatus(UpUserStatusDTO requestParam) {
        Long userId = requestParam.getId();
        User user = query().eq("id", userId).one();

        if (Objects.equals(user.getRole(), RoleEnum.admin.getRole())) {
            throw new ApiException("操作失败，管理员无法禁用自己");
        }

        boolean update = update()
                .set("status", requestParam.getStatus())
                .eq("id", userId)
                .update();
        if (!update) {
            throw new ApiException("内部错误，修改用户状态失败");
        }

        // 强制指定账号注销下线
        StpUtil.logout(userId);
    }

    /**
     * 校验验证码的有效性
     *
     * @param key  缓存键
     * @param code 验证码
     */
    private void verifyCode(String key, String code) {
        String cacheCode = redisTemplate.opsForValue().get(key);

        if (cacheCode == null) {
            throw new ApiException("验证码已过期，请重新获取验证码");
        }

        if (!Objects.equals(cacheCode, code)) {
            throw new ApiException("验证码错误，请检查输入");
        }
    }

}
